<template>
    <div>
        <!-- 主页的主体内容 -->
        <div class="top">
            <div class="top-op">
                <div class="btn">
                    <!-- :http-request: 选择文件后执行的方法 -->
                    <!-- :accept:       选择上传文件时的筛选 -->
                    <el-upload
                      :show-file-list="false"
                      :with-credentials="true"
                      :multiple="true"
                      :http-request="addFile"
                      :accept="fileAccept">
                        <el-button type="primary">
                            <span class="iconfont icon-upload"></span>
                            上传
                        </el-button>
                    </el-upload>
                </div>
                <el-button type="success"  @click="newFoloder">
                    <span class="iconfont icon-folder-add"></span>
                    新建文件夹
                </el-button>
                <el-button type="danger" :disabled="selectFileIdList.length == 0" @click="delFileBatch">
                    <span class="iconfont icon-del"></span>
                    批量删除
                </el-button>
                <el-button type="warning" :disabled="selectFileIdList.length == 0" @click="moveFolderBatch">
                    <span class="iconfont icon-move"></span>
                    批量移动
                </el-button>

                <!-- 自定义组件，批量移动的目录列表弹窗
                        @folderSelect通过子组件FolderSelect.vue触发 -- folderSelect方法中
                        ref="folderSelectRef" 可用于调用FolderSelect.vue子组件对外暴露的方法 -->
                <FolderSelect ref="folderSelectRef" @folderSelect="moveFolderDone"></FolderSelect>

                <!-- 搜索框  @keyup.enter: 回车时执行方法 -->
                <div class="search-panel">
                    <el-input clearable placeholder="请输入文件名搜索" v-model="fileNameFuzzy" @keyup.enter="search">
                        <template #suffix>
                            <i class="iconfont icon-search" @click="search"></i>
                        </template>
                    </el-input>
                </div>
                <!-- 搜索框后面的刷新按钮 -->
                <div class="iconfont icon-refresh" @click="loadDataList"></div>
            </div>

            <!-- 自定义组件：导航 -->
            <Navigation ref="navigationRef" @navChange="navChange"></Navigation>
        </div>

        <!-- 自定义组件：文件预览 -->
        <Preview ref="previewRef"></Preview>
        <!-- 自定义组件：文件分享 -->
        <ShareFile ref="shareRef"></ShareFile>

        <!-- tableData有数据时，遍历列表 -->
        <div class="file-list" v-if="tableData.list && tableData.list.length > 0">
            <!-- 自定义组件：列表展示
                     :dataSource：传递列表参数 
                     :fetch:      传递方法
                     @rowSelected：回调方法 -->
            <Table
              ref="dataTableRef"
              :columns="columns"
              :dataSource="tableData"
              :fetch="loadDataList"
              :initFetch="false"
              :options="tableOptions"
              @rowSelected="rowSelected"> 
                <!-- #fileName 与 186行定义的columns.scopedSlots绑定 -->
                <template #fileName="{ index, row }">
                    <!-- @mouseenter：鼠标悬停时，  @mouseleave：鼠标离开时 -->
                    <div class="file-item" @mouseenter="showOp(row)" @mouseleave="cancelShowOp(row)">

                      <!-- 展示预览封面 -->
                      <!-- 判断该项是否为视频或者图片，并转码成功 -->
                      <template v-if="(row.fileType == 1 || row.fileType == 3) && row.status == 2">
                        <!-- 传递封面给子组件 Icon.vue -->
                        <Icon :cover="row.fileCover" :width="65"></Icon>
                      </template>                    
                      <!-- 如果该项不是视频或图片，则 -->
                      <template v-else>
                        <!-- 判断为文件，则传递文件的类型给子组件，并匹配封面 -->
                        <Icon v-if="row.folderType == 0" :fileType="row.fileType"></Icon>
                        <!-- 判断为目录，则传递参数0给子组件 -->
                        <Icon v-if="row.folderType == 1" :fileType="0"></Icon>
                      </template>

                      <!-- 展示文件名 -->
                      <span class="file-name" v-if="!row.showEdit" :title="row.fileName">
                        <span @click="preview(row)">{{ row.fileName }}</span>
                        <span v-if="row.status == 0" class="transfer-status">转码中</span>
                        <span v-if="row.status == 1" class="transfer-status transfer-fail">转码失败</span>
                      </span>

                      <!-- 新建目录/重命名 时的编辑行 -->
                      <!-- 如果采用 v-show，该dom会被编译，只是根据row.showEdit隐藏与否的区别 -->
                      <!-- 会导致新建文件、重命名方法中的 editNameRef.value.focus 无法锁定是哪行 -->
                      <div class="edit-panel" v-if="row.showEdit">
                        <el-input 
                          v-model.trim="row.fileNameReal"
                          ref="editNameRef"
                          :maxlength="190"
                          @keyup.enter="saveNameEdit(index)">
                          <template #suffix>{{ row.fileSuffix }}</template>
                        </el-input>
                          <!-- saveNameEdit(index)，参数为57行，索引 -->
                        <span :class="['iconfont icon-right1', row.fileNameReal ? '' : 'not-allow']" @click="saveNameEdit(index)"></span>
                        <span class="iconfont icon-error" @click="cancelNameEdit(index)"></span>
                      </div>

                      <!-- 注意：v-show 跟 v-if 的区别 -->
                      <!-- 鼠标悬停时展示 -->
                      <span class="op">
                        <template v-if="row.showOp && row.fileId && row.status == 2">
                            <span class="iconfont icon-sharel" @click="share(row)">分享</span>
                            <span class="iconfont icon-download" v-if="row.folderType == 0" @click="download(row)">下载</span>
                            <span class="iconfont icon-del" @click="delFile(row)">删除</span>
                            <span class="iconfont icon-edit" @click="editFileName(index)">重命名</span>
                            <span class="iconfont icon-move" @click="moverFolder(row)">移动</span>
                        </template>
                      </span>

                    </div>
                </template>

                <!-- 展示文件大小 -->
                <template #fileSize="{ index, row }">
                    <span v-if="row.fileSize">{{ proxy.Utils.size2Str(row.fileSize) }}</span>
                </template>
            </Table>
        </div>

        <!-- tableData没数据时 -->
        <div class="no-data" v-else>
            <div class="no-data-inner">
                <Icon iconName="no_data" :width="120" fit="fill"></Icon>
                <div class="tips">当前目录为空，上传你的第一个文件吧</div>
                <div class="op-list">
                    <!-- :http-request: 选择文件后执行的方法 -->
                    <!-- :accept:       选择上传文件时的筛选 -->
                    <el-upload
                      :show-file-list="false"
                      :with-credentials="true"
                      :multiple="true"
                      :http-request="addFile"
                      :accept="fileAccept">
                        <div class="op-item">
                            <Icon iconName="file" :width="60"></Icon>
                            <div>上传文件</div>
                        </div>
                    </el-upload>
                    <div class="op-item" v-if="category == 'all'" @click="newFoloder">
                        <Icon iconName="folder" :width="60"></Icon>
                        <div>新建目录</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import CategoryInfo from '@/js/CategoryInfo.js'
import ShareFile from "./ShareFile.vue"
import { ref, reactive, getCurrentInstance, nextTick, onMounted, computed } from "vue";

const { proxy } = getCurrentInstance();

//请求定义
const api = {
    loadDataList: "/file/loadDataList",             //获取数据列表   
    rename: "/file/rename",                         //重命名
    newFoloder: "/file/newFoloder",                 //新建目录
    getFolderInfo: "/file/getFolderInfo",           //获取目录信息
    delFile: "/file/delFile",                       //删除文件
    changeFileFolder: "/file/changeFileFolder",     //移动文件
    createDownloadUrl: "/file/createDownloadUrl",   //创建下载链接
    download: "/api/file/download",                 //下载
};

const columns = [
    {
        label: "文件名",
        prop: "fileName",
        scopedSlots: "fileName",
    },
    {
        label: "修改时间",
        prop: "lastUpdateTime",
        width: 200
    },
    {
        label: "大小",
        prop: "fileSize",
        scopedSlots: "fileSize",
        width: 200,
    },
];

const tableData = ref({});        //分页查询数据集
const tableOptions = ref({
    extHeight: 50,
    selectType: "checkbox",
});

const showLoading = ref(true);             //是否开启 等待响应时的样式
const currentFolder = ref({fileId: "0"});  //用于记录当前所在路径(0代表根目录)
const fileNameFuzzy = ref({});             //搜索框绑定
const category = ref();                    //主页左边文件类型选择栏


/**
 * 搜索
 */
const search = () => {
    showLoading.value = true;
    loadDataList();
}


/**
 * 请求列表
 */
const loadDataList = async () => {
    let params = {
        //pageNo: tableData.value.pageNo,      //页码
        //pageSize: tableData.value.pageSize,  //每页显示数
        pageNo: 1,
        pageSize: 10,
        fileNameFuzzy: fileNameFuzzy.value,    //搜索文件名
        filePid: currentFolder.value.fileId,   //文件父id
        category: category.value,
    };

    if (params.category !== "all") {
        delete params.filePid;
    };
    //发送请求
    let result = await proxy.Request({
        url: api.loadDataList, //获取数据列表
        showLoading: showLoading.value,
        params: params,
        dataType: 'json',      //以json形式发送参数（默认是以文件形式）如不设置dataType，则后端无需使用@RequestBody接收对象
    });

    if (!result) {
        return;
    };
    tableData.value = result.data.dataList;
};


/**
 * 选择上传文件时的筛选
 */
const fileAccept = computed(() => {
    const categoryItem = CategoryInfo[category.value];
    return categoryItem ? categoryItem.accept : "*";
});


//上传完文件之后的回调  -- Framework.vue的uploadCallbackHandler方法调用
const reload = () => {
    showLoading.value= false;  //关闭 等待响应时的样式
    loadDataList();
};
defineExpose({ reload, });   //是Vue3中的一个新特性,它允许组件将其内部的数据和方法暴露给父组件


//鼠标悬停时，展示操作按钮
const showOp = (row) => {
    //遍历数据集，给每项赋于属性showOp，用于控制操作按钮
    tableData.value.list.forEach((element) => {
        element.showOp = false;
    });
    row.showOp = true;    //当前项为被鼠标选中，展示操作按钮
};


//鼠标离开时，隐藏操作按钮
const cancelShowOp = (row) => {
    row.showOp = false;
};


const emit = defineEmits(["addFile"]);     //子组件通过触发父组件的事件，并将参数传递到父组件
//上传按钮  -- 调用父组件views/Framework.vue中的addFile方法, Framework.vue第93行
const addFile = (fileData) => {
    emit("addFile", {file: fileData.file, filePid: currentFolder.value.fileId}); //currentFolder在<script>标签头部定义
};


const editing = ref(false);    //用于控制编辑行，如重命名、新建目录；false则代表没有行正在被编辑
const editNameRef = ref();
//新建文件夹按钮
const newFoloder = () => {
    if (editing.value) {
        return;       //有正在被编辑的行，无法新建目录
    }
    tableData.value.list.forEach((element) => {
        element.showEdit = false;  //给每项赋于属性showEdit
    });
    editing.value = true;          //代表当前已有行正在被编辑
    tableData.value.list.unshift({ //向数组的开头添加一个或多个元素，并返回新的长度
        showEdit: true,
        fileType: 0,
        fileId: "",
        filePid: currentFolder.value.fileId,
    });
    nextTick(() => {  //nextTick是dom节点改变才会执行，所以搭配v-if而不是v-show
        editNameRef.value.focus();     //光标自动选中编辑行
    });
};

//编辑行勾按钮 -- 新建文件夹、重命名
const saveNameEdit = async (index) => {
    const { fileId, filePid, fileNameReal } = tableData.value.list[index]; //获取对应的值
    if (fileNameReal == "" || fileNameReal.indexOf("/") != -1) {  //新建文件夹、重命名的输入框绑定的属性为fileNameReal
        proxy.Message.warning("文件名不能为空，不能含有斜杠");
    }

    let url = api.rename;     //重命名
    if (fileId == "") {
        url = api.newFoloder; //新建文件夹
    }

    let result = await proxy.Request({
        url: url,
        params: {
            fileId: fileId,
            filePid: filePid,
            fileName: fileNameReal
        },
    });
    if (!result) {
        return;
    }
    tableData.value.list[index] = result.data.fileData; //更新数据
    editing.value = false;     //在223行中被定义
}


//编辑行X按钮 -- 新建文件夹、重命名
const cancelNameEdit = (index) => {
    const fileData = tableData.value.list[index];  //获取当前行
    if (fileData.fileId) {
        fileData.showEdit = false;                 //如果当前行有Id值，则代表此次操作为重命名，直接关闭
    } else {                                       //到此代表此次操作为新建文件夹
        tableData.value.list.splice(index, 1);     //从索引index开始，删掉一条数据，并返回被删除的数据
    }
    editing.value = false;                         //在218行中被定义
}


//重命名按钮
const editFileName = (index) => {
    if (tableData.value.list[0].fileId == "") {    //id为空则是点击了 新建文件夹 按钮
        tableData.value.list.splice(0, 1);         //从索引0开始，删掉一条数据，并返回被删除的数据
        index = index  - 1;                        //因为删掉了正在编辑的行，所以索引-1
    }

    tableData.value.list.forEach((element) => {
        element.showEdit = false;
    })
    let currentData = tableData.value.list[index];
    currentData.showEdit = true;                   //显示编辑框

    if (currentData.folderType == 0) {             // == 0 是重命名文件，否则是重命名目录
        currentData.fileNameReal = currentData.fileName.substring(0, currentData.fileName.indexOf("."));
        currentData.fileSuffix = currentData.fileName.substring(currentData.fileName.indexOf(".")); //截取后缀名
    } else {
        currentData.fileNameReal = currentData.fileName;  //用于回显的文件名
        currentData.fileSuffix = "";                      //截取后缀名
    }
    editing.value = true;            //在223行中被定义，用于标识当前是否有正在编辑的行
    nextTick(() => {
        editNameRef.value.focus();     //光标自动选中编辑行
    })
};


const selectFileIdList = ref([]);    //被选中的行列
//多选 由子组件Table.vue的handelSelectionChange方法调用
const rowSelected = (rows) => {
    selectFileIdList.value = [];
    rows.forEach((item) => {
        selectFileIdList.value.push(item.fileId);   //添加
    });
};


//批量删除按钮
const delFileBatch = () => {
    if (selectFileIdList.value.length == 0) {
        return;
    }
    proxy.Confirm("是否确定批量删除？删除后可在10天内通过回收站还原", async () => {
        let result = await proxy.Request({
            url: api.delFile,
            params: { fileIds: selectFileIdList.value.join(","), }, //将数组中所有的元素合并为一个字符串，元素之间通过指定字符进行分隔
        });
        if (!result) {
            return;
        };
        loadDataList();   //重新请求列表
    })
};


//操作项中的单个删除按钮
const delFile = (row) => {
    proxy.Confirm(`是否确定删除【${row.fileName}】？删除后可在10天内通过回收站还原`, async () => {
        let result = await proxy.Request({
            url: api.delFile,
            params: { fileIds: row.fileId }, 
        });
        if (!result) {
            return;
        };
        loadDataList();   //重新请求列表
    })
};


const folderSelectRef = ref();  //FolderSelect子组件对象
const currentMoveFile = ref({});//需移动的文件
//批量移动按钮
const moveFolderBatch = () => {
    currentMoveFile.value = {};
    //调用 FolderSelect.vue子组件 方法
    folderSelectRef.value.showFolderDialog(currentFolder.value.fileId);  //currentFolder在<script>标签头部定义 
};


//操作项中的单个移动按钮
const moverFolder = (data) => {
    currentMoveFile.value = data;
    folderSelectRef.value.showFolderDialog(currentFolder.value.fileId); //调用 FolderSelect.vue子组件 方法
}


//确定移动文件
//通过子组件FolderSelect.vue触发 -- folderSelect方法中
const moveFolderDone = async (folderId) => {
    if (currentFolder.value.fileId == folderId) {    //判断当前路径 与 目的地目录是否一致
        proxy.Message.warning("文件已在当前目录，无需移动");
        return;
    };
    //准备参数
    let fileIdsArray = [];               //需移动的文件（没有ref(), 所以无需 .value）
    if (currentMoveFile.value.fileId) {  //判断是否为单项移动
        fileIdsArray.push(currentMoveFile.value.fileId);    
    } else {
        //selectFileIdList在多选方法上方定义
        fileIdsArray = fileIdsArray.concat(selectFileIdList.value);  //concat：合并数组
    }
    //调用接口
    let result = await proxy.Request({
        url: api.changeFileFolder,
        params: {
            fileIds: fileIdsArray.join(","),  //生成一个字符串，元素之间以逗号分隔
            filePid: folderId,
        },
    });

    if (!result) {
        return;
    };

    folderSelectRef.value.close(); //调用 FolderSelect.vue子组件 方法, 关闭弹窗
    loadDataList();                //重新请求列表

};


/**
 * 文件下载按钮
 */
const download = async (row) => {
    let result = await proxy.Request({
        url: api.createDownloadUrl + "/" + row.fileId,    //获取code
    });
    if (!result) {
        return;
    }
    window.location.href = api.download + "/" + result.data.downloadDto.downloadCode;  //下载
};


/**
 * 分享按钮
 */
const shareRef = ref();
const share = (data) => {
    shareRef.value.show(data);  //调用子组件ShareFile.vue的show方法
}


/** 
 * 点击文件
*/
const navigationRef = ref();
const previewRef = ref();
const preview = (data) => {
    fileNameFuzzy.value = ref({});     //清空搜索框
    //点击的是文件夹，则进入目录内
    if (data.folderType == 1) {
        navigationRef.value.openFolder(data);
        return;
    }
    //上一条if不成立，则为文件。判断文件是否转码成功
    if (data.status != 2) {
        proxy.Message.warning("文件未完成转码，无法预览");
        return
    }
    previewRef.value.showPreview(data, 0);  //文件预览。 参数：文件，类型（0：普通用户、1：管理员、2：文件分享）
};


//所在目录（导航）改变回调，由子组件 Navigation.vue 的 doCallback方法调用
const navChange = (data) => {
    const {categoryId, curFolder} = data;
    currentFolder.value = curFolder;      //更新当前所在目录
    category.value = categoryId;          //主页左边分类栏  
    fileNameFuzzy.value = ref({});        //清空搜索框
    loadDataList();
};

</script>
<style lang="scss" scoped>
@import "@/assets/file.list.scss";
</style>
